/*
 * File Name: httpstuff.c
 *
 * Group Members: Cassie Chin (cassiechin9793@gmail.com)
 *                Stephen Smetana (stephensmetana11@gmail.com)
 *                Logan Widick (logan.widick@gmail.com)
 *
 * File Description:
 *
 *
 */

#include "httpstuff.h" 
#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/stat.h>

#define MIN(X,Y) ((X) < (Y) ? (X) : (Y))
/**
 * Contains the status code, the reason-phrase, and whether or not a body can be found.
 */
static HTTPResponseCode responseCodes[] = { { 100, "Continue", false }, { 101,
		"Switching Protocols", false }, { 200, "OK", false }, { 201, "Created",
		false }, { 202, "Accepted", false }, { 203,
		"Non-Authoritative Information", false }, { 204, "No Content", false },
		{ 205, "Reset Content", false }, { 206, "Partial Content", false }, {
				300, "Multiple Choices", true }, { 301, "Moved Permanently",
				true }, { 302, "Found", true }, { 303, "See Other", true }, {
				304, "Not Modified", false }, { 305, "Use Proxy", true }, { 306,
				"(Unused)", true }, { 307, "Temporary Redirect", true }, { 400,
				"Bad Request", true }, { 401, "Unauthorized", true }, { 402,
				"Payment Required", true }, { 403, "Forbidden", true }, { 404,
				"Not Found", true }, { 405, "Method Not Allowed", true }, { 406,
				"Not Acceptable", true }, { 407,
				"Proxy Authentication Required", true }, { 408,
				"Request Timeout", true }, { 409, "Conflict", true }, { 410,
				"Gone", true }, { 411, "Length Required", true }, { 412,
				"Precondition Failed", true }, { 413,
				"Request Entity Too Large", true }, { 414,
				"Request-URI Too Long", true }, { 415, "Unsupported Media Type",
				true }, { 416, "Requested Range Not Satisfiable", true }, { 417,
				"Expectation Failed", true }, { 500, "Internal Serval Error",
				true }, { 501, "Not Implemented", true }, { 502, "Bad Gateway",
				true }, { 503, "Service Unavailable", true }, { 504,
				"Gateway Timeout", true }, { 505, "HTTP Version Not Supported",
				true },

};

/**
 * Returns the reason-phrase for the code
 * @param code
 * @return a string. DO NOT FREE THIS!
 */
char* getReasonPhraseForCode(int code) {
	int maxSize = sizeof(responseCodes) / sizeof(HTTPResponseCode);
	int min = 0;
	int mid = maxSize / 2;
	int max = maxSize - 1;
	while (min <= max) {
		mid = max + mid / 2;
		if (responseCodes[mid].num == code) {
			return responseCodes[mid].desc;
		}
		//mid > code; go back
		else if (responseCodes[mid].num > code) {
			max = mid - 1;
		}
		//mid < code; go up
		else if (responseCodes[mid].num < code) {
			min = mid + 1;
		}
	}

	//if we are still here issue default code.
	//1xx Informational
	if (code / 100 == 1) {
		return "Informational Request Received Continuing";
	}
	//2xx success
	else if (code / 100 == 2) {
		return "Successful";
	}
	//3xx Redirect
	else if (code / 100 == 3) {
		return "Redirect";
	}
	//4xx client error
	else if (code / 100 == 4) {
		return "Client Error";
	}
	//5xx Server Error
	else if (code / 100 == 5) {
		return "Informational Request Received Continuing";
	}
	//ok this should not happen.
	else {
		return NULL;
	}
}

bool isBodyOk(int code) {
	int maxSize = sizeof(responseCodes) / sizeof(HTTPResponseCode);
	int min = 0;
	int mid = maxSize / 2;
	int max = maxSize - 1;
	while (min <= max) {
		mid = max + mid / 2;
		if (responseCodes[mid].num == code) {
			return responseCodes[mid].canHaveBody;
		}
		//mid > code; go back
		else if (responseCodes[mid].num > code) {
			max = mid - 1;
		}
		//mid < code; go up
		else if (responseCodes[mid].num < code) {
			min = mid + 1;
		}
	}

	//assume we can
	return true;
}

/******************************************************************************/
/*                         START HEADER STUFF                                 */
/******************************************************************************/
HeaderList* new_HeaderList() {
	HeaderList *self = calloc(1, sizeof(HeaderList));

	self->head = NULL;
	self->tail = NULL;

	return self;
}
void delete_HeaderList(HeaderList *list) {
	HeaderListNode *nxt_iter = NULL;
	HeaderListNode *iter = list->head;

	while (iter != NULL) {
		nxt_iter = iter->next;
		if (iter->name) free(iter->name);
		if (iter->value) free(iter->value);
		if (iter) free(iter);
		iter = nxt_iter;
	}
	if (list) free(list);
}

/**
 * Adds a new header given the name and value
 * @param list
 * @param name
 * @param value
 */
void HeaderList_addHeader(HeaderList *list, char *name, char *value) {
	if (!list)
		list = new_HeaderList();
	if (!list)
		return;

	// Allocate memory for the node and set values
	HeaderListNode *ptr2HeaderNode = (HeaderListNode*) calloc(1,
			sizeof(HeaderListNode));
	ptr2HeaderNode->name = strdup(name);
	ptr2HeaderNode->value = strdup(value);
	ptr2HeaderNode->next = NULL;

	if (list->tail) {
		// Set the current tail's next pointer, to point to the new node.
		list->tail->next = ptr2HeaderNode;
		// Set the tail of the list to the new node.
		list->tail = ptr2HeaderNode;
	} else {
		list->head = ptr2HeaderNode;
		list->tail = ptr2HeaderNode;
	}
}

/**
 * returns true if header exists
 * @param list
 * @param name
 * @return
 */
bool HeaderList_doesHeaderExist(HeaderList *list, char *name) {
	if (!list) {
		return false;
	}
	HeaderListNode *iter = list->head;
	for (iter = list->head; iter != NULL; iter = iter->next) {
		if (!iter->name || !iter->value)
			continue;
		if (strcmp(name, iter->name) == 0) {
			return true;
		}
	}
	return false;
}

/**
 * Returns a pointer to the value for a header.
 * THIS METHOD DOES NOT COPY SO PLEASE DO NOT ALTER THE RETURNED VALUE. DO NOT FREE.
 * @param list the header list
 * @param name the name of the header.
 */
char *HeaderList_getValue(HeaderList *list, char *name) {
	if (!list) {
		return NULL;
	}
	HeaderListNode *iter = list->head;
	for (iter = list->head; iter != NULL; iter = iter->next) {
		if (!iter->name || !iter->value)
			continue;
		if (strcmp(name, iter->name) == 0) {
			return iter->value;
		}
	}
	return NULL;
}

/**
 * Appends a new value to the last header in the list.
 * @param list
 * @param value
 */
void HeaderList_addToLastHeader(HeaderList *list, char *value) {
	if (!list)
		return;
	if (!list->tail)
		return;

	// TODO: we need to check if this header can take multiple values. For example, content length can only have one value

	char *tmpbuf = (char *) calloc(
			strlen(value) + strlen(list->tail->value) + 2, sizeof(char));
	if (!tmpbuf)
		return;

	/*
	 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
	 * Multiple message-header fields with the same field-name MAY be present
	 * in a message if and only if the entire field-value for that header field
	 * is defined as a comma-separated list [i.e., #(values)].
	 * It MUST be possible to combine the multiple header fields into one
	 * "field-name: field-value" pair, without changing the semantics of the message,
	 * by appending each subsequent field-value to the first, each separated by a comma.
	 * The order in which header fields with the same field-name are received is therefore
	 * significant to the interpretation of the combined field value, and thus a proxy
	 * MUST NOT change the order of these field values when a message is forwarded.
	 */
	strcat(tmpbuf, list->tail->value);
	strcat(tmpbuf, ",");
	strcat(tmpbuf, value);

	//we have a new buffer, so out with the old
	if (list->tail->value) free(list->tail->value);

	//and in with the new
	list->tail->value = tmpbuf;
}

/******************************************************************************/
/*                         END HEADER STUFF                                   */
/******************************************************************************/

/*****************************************************************************/
/*                        BEGIN FILE PTR SET STUFF                           */
/*****************************************************************************/

/**
 * Opens a client connection given the port, ip, and a pointer to a FilePointerSet to be filled
 * @param PORT the port # as a string
 * @param IP the IP address to connect to
 * @param ptrSet the pointer to the FilePointerSet to fill.
 * @return 0 on success, something negative on fail.
 */
int openClientConnection(char *PORT, char *IP, FilePointerSet *ptrSet) {
	if(!PORT || !IP || !ptrSet) return -1;

	//Connection code adapted from 
        //http://beej.us/guide/bgnet/output/html/multipage/clientserver.html#simpleclient 

	struct addrinfo hints, *servinfo, *p;
	int sockfd = 0;
	int rv=0;

	memset(&hints, 0, sizeof hints);
	memset(ptrSet, 0, sizeof(FilePointerSet));

	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

	if ((rv = getaddrinfo(IP, PORT, &hints, &servinfo)) != 0) {
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
		return -1;
	}

	// loop through all the results and connect to the first we can
	for(p = servinfo; p != NULL; p = p->ai_next) {
		if ((sockfd = socket(p->ai_family, p->ai_socktype,
								p->ai_protocol)) == -1) {
			perror("client: socket");
			continue;
		}

		if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
			close(sockfd);
			perror("client: connect");
			continue;
		}

		break;
	}
	if (p == NULL) {
		fprintf(stderr, "client: failed to connect\n");
		return -2;
	}

	//now set the file.
	ptrSet->inFile = fdopen(sockfd, "r");
	ptrSet->outFile = fdopen(dup(sockfd), "w");
	setvbuf(ptrSet->inFile, NULL, _IONBF, 0);
	setvbuf(ptrSet->outFile, NULL, _IONBF, 0);
	return 0;
}
/*****************************************************************************/
/*                        END   FILE PTR SET STUFF                           */
/*****************************************************************************/

/******************************************************************************/
/*                         START HTTP STUFF                                   */
/******************************************************************************/

/**
 * Constructor
 * @return
 */
HTTPMsg *new_HTTPMsg() {
	HTTPMsg *self = (HTTPMsg *) calloc(1, sizeof(HTTPMsg));
	if (!self)
		return NULL;

	//the other fields are set to null via calloc.
	self->headerList = new_HeaderList();
	self->contentLength = CONTENT_LENGTH_NOT_SET;
	return self;
}

/**
 * destructor
 * @param msg
 */
void delete_HTTPMsg(HTTPMsg *msg) {
	if (msg->method)
		if (msg->method) free(msg->method);
	if (msg->headerList)
		delete_HeaderList(msg->headerList);
	if (msg->resource)
		if (msg->resource) free(msg->resource);
	if (msg->fileptr)
		fclose(msg->fileptr);
	if (msg) free(msg);
}

bool isHex(char ch){
    if(ch >= '0' && ch <= '9') return true;
    else if(ch >= 'a' && ch <= 'f') return true;
    else if(ch >= 'A' && ch <= 'F') return true;
    else return false;
}

char *decode_url(char *url){
    if(!url) return NULL;
     int len = strlen(url);
    char *retbuf = calloc(len+1, sizeof(char));    
    int i = 0;
    int j = 0;
    char hexbuf[3]; 
    int chars_after_perc  = 0;
    for(i=0; i < len; i++){
        //we see percent char, but are still trying to decode the previous percent encoded. Error. 
        if(url[i] == '%' && chars_after_perc != 0){
            printf("Error with chars_after_perc\n");
            free(retbuf);
            return NULL;
        }
        //we see percent char; begin percent encode state
        else if(url[i] == '%'){
            chars_after_perc = 2;
        }
        //we see hex and expect chars after perc. Add to hexbuffer
        else if(isHex(url[i]) && chars_after_perc > 0){
            hexbuf[2-chars_after_perc] = url[i];
            chars_after_perc--;
            hexbuf[2] = '\0';
            
            //we have finished filling hexbuffer. Scan and store in retbuf 
            if(chars_after_perc == 0){
                int num = 0;
                sscanf(hexbuf, "%x", &num);
                retbuf[j] = (char)num;
                j++;
            }
        }
        
        //we see nonhex and expect hex. Error!
        else if(!isHex(url[i]) && chars_after_perc != 0){
            printf("NonHex given when hex expected\n");
            free(retbuf);
            return NULL;
        }
        
        //we see a plus sign. Copy as space.
        else if(url[i] == '+'){
            retbuf[j] = ' ';
            j++;
        }
        
        //copy over and continue
        else {
            retbuf[j] = url[i];
            j++;
        }
    }
    return retbuf;
}

/**
 * Extracts the path to the file found in the message's resource.
 * @param msg the message
 * @return the path to the file on the server (assuming that root is the current directory),
 * or NULL if the url cannot be parsed. 
 */
char* HTTPMsg_findFilePath(HTTPMsg *msg) {
	if (!msg)
		return NULL;
   
   //DO NOT FREE theresource. 
  char *theresource = HeaderList_getValue(msg->headerList, "Content-Location");
  if(!theresource) theresource = msg->resource;
  //insert other possible resources here. 
	if (!theresource) return NULL;
   
	/*
	 * Explanation:
	 * Group 0: the whole thing
	 * SubGroup 1: name of url scheme with colon but no slashes
	 * SubGroup 2: name of url scheme with no colon or slashes
	 * SubGroup 3: the name of the website with two slashes in front of it
	 * SubGroup 4: the name of the website with no slashes in front
	 * SubGroup 5: Either the path to the resource starting from the root of the site (/)
	 * 				or the name of the website (no slashes)
	 *				and the path to the resource starting at the root
	 *				(THIS IS THE BIG ONE HERE!!!!)
	 * SubGroups 6-7: mainly used for supporting special features like questionmarks and pound targets.
	 *
	 * This regex was taken from http://tools.ietf.org/html/rfc3986#page-50
	 * but modified so that it would actually compile and match stuff.
	 *
	 * For this function, only subgroup 5 is relevant.
	 */
	regex_t regex;
	regcomp(&regex, "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)([^#]*)?(#(.*))?",
			REG_EXTENDED);
	regmatch_t *matches = calloc(regex.re_nsub + 1, sizeof(regmatch_t));
	char *match_buffer = NULL;
	
	regex_t subregex;
	/*
	 * The subregex to weed out stuff that appears before the path (like google.com in google.com/folder)
	 * Again, only SubGroup 5 is important.
	 */
	regcomp(&subregex, "(([^/?#])*)?((/)*)(([^#?])*)?", REG_EXTENDED);
	regmatch_t *submatches = calloc(subregex.re_nsub, sizeof(regmatch_t));
  
	//regexec returns 0 if successful
	if(regexec (&regex, theresource, regex.re_nsub, matches, 0) == 0) {
	  //copy into new string
	  char *new_buffer = calloc(matches[5].rm_eo - matches[5].rm_so + 1, sizeof(char));
	  strncpy(new_buffer, &(theresource[matches[5].rm_so]), matches[5].rm_eo - matches[5].rm_so);
	  
	  //now apply the subregex 
	  if(regexec(&subregex, new_buffer, subregex.re_nsub, submatches, 0) == 0){
	    //copy into match buffer
	    match_buffer = calloc(submatches[5].rm_eo - submatches[5].rm_so + 5, sizeof(char));
	    strcpy(match_buffer, "./");
	    strncat(match_buffer, &(new_buffer[submatches[5].rm_so]), (submatches[5].rm_eo - submatches[5].rm_so));  
	    
	  }
     
    //handle url decode
    char *ret_buffer = decode_url(match_buffer);
    if(!ret_buffer){
        free(match_buffer);
        match_buffer = NULL;     
    }
    else{
      free(match_buffer);
      match_buffer = ret_buffer;
    }
    
    if (new_buffer) free(new_buffer);  
    
    if(match_buffer)
    {
      // If the result is a directory append index.html onto the end
       struct stat s;
       if( stat(match_buffer,&s) == 0 ) {
          if( s.st_mode & S_IFDIR ) {
             char *append_buffer = calloc(strlen(match_buffer)+20, sizeof(char));
             strcat(append_buffer, match_buffer);
             strcat (append_buffer, "/index.html");
             free(match_buffer);
             match_buffer = append_buffer;
          }
       }
    }
     
	}

	//cleanup
	regfree(&regex);
	if (matches) free(matches);
	regfree(&subregex);
	if (submatches) free(submatches);
	
	//exit
	return (match_buffer);
}

/**
 *
 * @param msg
 * 		The message to send out. Must be preset with the content-length, file* (with the data; if applicable),
 * 		and whether or not it is a request or a response.
 * 		All fields applicable to the type of message must also be set.
 * 		The headers must also be present (except content-length which can be END automatically).
 * 		May attempt to send transfer-encoding if not present.
 *
 * @param sock_out_filep
 * @return 0 on success, or a negative error if FILE* is closed prematurely.
 */
int HTTPMsg_sendMsg(HTTPMsg *msg, FILE* sock_out_filep) {

	//use this line after implementing getReasonPhraseForCode
	char *reasonPhrase = NULL;

	//convert version to string
	char numbuf[NUMBER_BUFFER_SIZE + 1];
	char numbuf2[NUMBER_BUFFER_SIZE + 1];

	snprintf(numbuf, NUMBER_BUFFER_SIZE, "HTTP/%f", msg->version);

	//strings for spaces and newlines
	char sp[] = " ";
	char newline[] = "\r\n";
	char colon_space[] = ": ";
	char content_length[] = "Content-Length";
	char xfer_encoding[] = "Transfer-Encoding";
	char chunked[] = "Chunked";

	//print out first line
	if (msg->isRequest) {
		//request format: method SP resource SP HTTP/version
		fwrite(msg->method, sizeof(char), strlen(msg->method), sock_out_filep);
		fwrite(sp, sizeof(char), strlen(sp), sock_out_filep);
		fwrite(msg->resource, sizeof(char), strlen(msg->resource),
				sock_out_filep);
		fwrite(sp, sizeof(char), strlen(sp), sock_out_filep);
		fwrite(numbuf, sizeof(char), strlen(numbuf), sock_out_filep);
		fwrite(newline, sizeof(char), strlen(newline), sock_out_filep);
		fflush(sock_out_filep);
	}

	//WE ARE DOING RESPONSE HERE
	else {
		reasonPhrase = getReasonPhraseForCode(msg->statusCode);

		//get status code
		snprintf(numbuf2, NUMBER_BUFFER_SIZE, "%d", msg->statusCode);

		//output format: HTTP/version SP status-code SP reason-phrase
		fwrite(numbuf, sizeof(char), strlen(numbuf), sock_out_filep);
		fwrite(sp, strlen(sp), 1, sock_out_filep);
		fwrite(numbuf2, sizeof(char), strlen(numbuf2), sock_out_filep);
		fwrite(sp, sizeof(char), strlen(sp), sock_out_filep);
		fwrite(reasonPhrase, sizeof(char), strlen(reasonPhrase),
				sock_out_filep);
		fwrite(newline, sizeof(char), strlen(newline), sock_out_filep);
		fflush(sock_out_filep);
		//DO NOT FREE reason phrase.
	}

	//print out headers
	HeaderListNode *iter = NULL;
	if (msg->headerList) {
		for (iter = msg->headerList->head; iter != NULL; iter = iter->next) {
			//format: Name: Value
			fwrite(iter->name, sizeof(char), strlen(iter->name),
					sock_out_filep);
			fwrite(colon_space, sizeof(char), strlen(colon_space),
					sock_out_filep);
			fwrite(iter->value, sizeof(char), strlen(iter->value),
					sock_out_filep);
			fwrite(newline, sizeof(char), strlen(newline), sock_out_filep);
			fflush(sock_out_filep);
		}
	}

	//if we have a content-length print it out
	if (msg->contentLength > 0) {
		fwrite(content_length, sizeof(char), strlen(content_length),
				sock_out_filep);
		fwrite(colon_space, sizeof(char), strlen(colon_space), sock_out_filep);

		//use the buffer.
		snprintf(numbuf, NUMBER_BUFFER_SIZE - 1, "%d", msg->contentLength);

		//write to file
		fwrite(numbuf, sizeof(char), strlen(numbuf), sock_out_filep);
		fwrite(newline, sizeof(char), strlen(newline), sock_out_filep);
		fflush(sock_out_filep);
	}
	//write transfer encoding header unless already there
	else if (msg->contentLength == CHUNKED_LENGTH) {
		if (HeaderList_doesHeaderExist(msg->headerList, xfer_encoding)) {
			fwrite(xfer_encoding, sizeof(char), strlen(xfer_encoding),
					sock_out_filep);
			fwrite(colon_space, sizeof(char), strlen(colon_space),
					sock_out_filep);
			fwrite(chunked, sizeof(char), strlen(chunked), sock_out_filep);
			fwrite(newline, sizeof(char), strlen(newline), sock_out_filep);
			fflush(sock_out_filep);
		}
	}

	//print out blank line
	fwrite(newline, strlen(newline), 1, sock_out_filep);
	fflush(sock_out_filep);
	
	//print out body (should be the FILE* in the message)
	char buffer[CHUNK_SIZE + 1];
	int numBytes = 0;
	if (msg->fileptr) {
		rewind(msg->fileptr);

		if (msg->contentLength == CHUNKED_LENGTH) {
			//go through the file, sending CHUNK_SIZE bytes each time.
			while (!feof(msg->fileptr)) {
				numBytes = fread(buffer, sizeof(char), CHUNK_SIZE,
						msg->fileptr);
				if (numBytes <= 0)
					break;

				//chunk len
				snprintf(numbuf, NUMBER_BUFFER_SIZE, "%d\r\n", numBytes);
				fwrite(numbuf, sizeof(char), strlen(numbuf), sock_out_filep);

				//chunk data
				fwrite(buffer, sizeof(char), numBytes, sock_out_filep);

				//newline after chunk
				fwrite(newline, sizeof(char), strlen(newline), sock_out_filep);
				fflush(sock_out_filep);
			}

			//now for the terminating 0 chunk
			//chunk len
			snprintf(numbuf, NUMBER_BUFFER_SIZE, "0\r\n");
			fwrite(numbuf, sizeof(char), strlen(numbuf), sock_out_filep);
		}

		//we are not using chunks
		else {
			//we are not using chunks; just send the entire file.
			while (!feof(msg->fileptr)) {
				numBytes = fread(buffer, sizeof(char), CHUNK_SIZE,
						msg->fileptr);
				if (numBytes <= 0)
					break;
				fwrite(buffer, sizeof(char), numBytes, sock_out_filep);
				fflush(sock_out_filep);
			}
		}
	}
	fwrite(newline, sizeof(char), strlen(newline), sock_out_filep);
	fflush(sock_out_filep);
	return 0;
}

/**
 * Parses the data from sock_fp_in into msg
 * @param msg The HTTPMsg. Must have isRequest set accordingly, and all other variables cleared.
 * @param sock_fp_in the file descriptor for the socket.
 * @return 0 on success, -1 on error, -2 on the FILE*'s EOF.
 */
int HTTPMsg_doParsing(HTTPMsg *msg, FILE* sock_fp_in) {
	int numObtained = 0;
	int numParsed = 0;
	int numRead = 0;
	int chunkLen = 0;
	char dummyBuffer[BUFFER_SIZE + 1];
	char buffer[BUFFER_SIZE + 1];
	char buffer2[BUFFER_SIZE + 1];
	char buffer3[BUFFER_SIZE + 1];
	bool willNeedConcat = false;
	msg->contentLength = CONTENT_LENGTH_NOT_SET;
	msg->state = FIRSTLINE;
	while (fgets(buffer, BUFFER_SIZE, sock_fp_in)) {
		numObtained = strlen(buffer);
		if (msg->contentLength == CHUNKED_LENGTH || msg->state != BODY) {
			//remove trailing \r\n
			if (buffer[numObtained - 1] == '\r'
					|| buffer[numObtained - 1] == '\n') {
				buffer[numObtained - 1] = '\0';
				numObtained--;
			}
			if (buffer[numObtained - 1] == '\r'
					|| buffer[numObtained - 1] == '\n') {
				buffer[numObtained - 1] = '\0';
				numObtained--;
			}
		}

		//now let's get to the parsing!
		if (msg->state == FIRSTLINE) {
			//response
			if (!msg->isRequest) {
				numParsed = sscanf(buffer, "HTTP/%f %d %s", &(msg->version),
						&(msg->statusCode), buffer2);
				if (numParsed < 3) {
					printf("Error parsing response. numParsed: %d\n",
							numParsed);
					return -1;
				}
				//strdup to struct
				msg->reasonPhrase = strdup(buffer2);
				printf(
						"Received HTTP Response with version: %f code: %d and phrase: %s\n",
						msg->version, msg->statusCode, msg->reasonPhrase);
				fflush(stdout);
			}

			//request
			else {
				numParsed = sscanf(buffer, "%s %s HTTP/%f", buffer2, buffer3,
						&(msg->version));
				if (numParsed < 3) {
					printf("Error parsing request. numParsed: %d\n", numParsed);
					return -1;
				}
				//strdup to struct
				msg->method = strdup(buffer2);
				msg->resource = strdup(buffer3);
				printf(
						"Received HTTP Request with method: %s resource: %s version: %f\n",
						msg->method, msg->resource, msg->version);
				fflush(stdout);
			}
			msg->state = HEADERS;
		}

		//head
		else if (msg->state == HEADERS) {
			//blank line: go to body
			if (numObtained <= 0) {
				msg->state = BODY;
				if (msg->isRequest
						&& msg->contentLength == CONTENT_LENGTH_NOT_SET) {
					printf(
							"We do not have a content length in the request. Exiting.\n");
					fflush(stdout);
					msg->state = END;
					break;
				} else if (!msg->isRequest && !isBodyOk(msg->statusCode)) {
					printf(
							"We are not supposed to have a body in this response. Exiting.\n");
					fflush(stdout);
					msg->state = END;
					break;
				}
			}

			//space; add to prev
			else if (isspace(buffer[0])) {
				char *iter = buffer;

				//go to first nonwhitespace char?
				while (isspace(*iter))
					iter++;

				//add to previous header
				HeaderList_addToLastHeader(msg->headerList, iter);
			}
			//we do not have space
			else {
				memset(buffer2, 0, sizeof(buffer2));
				memset(buffer3, 0, sizeof(buffer3));
				sscanf(buffer, "%[^ :] %[:] %[^:\r\n]", buffer2, dummyBuffer,
						buffer3);
				printf("Received header %s and value %s\n", buffer2, buffer3);
				fflush(stdout);
				HeaderList_addHeader(msg->headerList, buffer2, buffer3);
				//check for content length
				if (strcmp(buffer2, "Content-Length") == 0) {
					sscanf(buffer3, "%d", &(msg->contentLength));
					printf("Detected content length %d\n", msg->contentLength);
					fflush(stdout);
				}

				if (strcmp(buffer2, "Transfer-Encoding") == 0) {
					//transfer encoding means "we chunk" unless "identity"
					if (strstr(buffer3, "identity") == NULL) {
						msg->contentLength = CHUNKED_LENGTH;
						printf("We are using CHUNKED encoding now!\n");
						fflush(stdout);
					}
				}
			}
		} else if (msg->state == BODY) {
			//we have a body; and a content length too!
			if (msg->contentLength > 0) {
				//add to msg data
				if (msg->fileptr == NULL) {
					msg->fileptr = tmpfile();
				}
				int number_to_read =
						MIN((numObtained), (msg->contentLength - numRead));
				fwrite(buffer, sizeof(char), number_to_read, msg->fileptr);
				fflush(msg->fileptr);
				printf("Wrote to file pointer. Position: %ld\n",
						ftell(msg->fileptr));

				//NOTE: Caller is responsible for closing msg->fileptr.
				numRead += number_to_read;
				if (numRead >= msg->contentLength) {
					msg->state = END;
					break;
				}
			}
			//chunky: go until 0 len chunk
			else if (msg->contentLength == CHUNKED_LENGTH) {
				if (chunkLen == 0) {
					sscanf(buffer, "%d", &chunkLen);
					if (chunkLen <= 0) {
						msg->state = END;

						//The following break statement exits immediately, 
						//without allowing the consumption of the empty chunk.
						//break;
					}
				} else {
					//add to msg data
					if (msg->fileptr == NULL) {
						msg->fileptr = tmpfile();
					}

					fwrite(buffer, sizeof(char), chunkLen, msg->fileptr);
					fflush(msg->fileptr);
					chunkLen = 0; //reset for next chunk
				}
			}

			//no idea of length; keep going til eof
			else {
				//add to msg data
				if (msg->fileptr == NULL) {
					msg->fileptr = tmpfile();
				}
				fwrite(buffer, sizeof(char), numObtained, msg->fileptr);
				fflush(msg->fileptr);
			}
		}

		//additional states here
		else {
			break;
		}
	}
	return 0;
}
/******************************************************************************/
/*                           END HTTP STUFF                                   */
/******************************************************************************/

